package mapper.api;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import mapper.annotation.ResponseResult;
import mapper.base.IPlugin;
import mapper.context.PluginContext;
import mapper.model.ResponseData;
import mapper.utils.CollectionUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class RequestMappingService implements InitializingBean, IPlugin {

	@Resource
	private RequestMappingHandlerMapping handlerMapping;

	private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

	@Autowired
	private PluginContext pluginContext;

	/**
	 * 核心配置类，因为`RequestMappingHandlerMapping`中config属性未提供公共方法，所以需要自行构建
	 * @throws Exception
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		this.config.setTrailingSlashMatch(handlerMapping.useTrailingSlashMatch());
		this.config.setContentNegotiationManager(handlerMapping.getContentNegotiationManager());

		if (handlerMapping.getPatternParser() != null) {
			this.config.setPatternParser(handlerMapping.getPatternParser());
			Assert.isTrue(!handlerMapping.useSuffixPatternMatch() && !handlerMapping.useRegisteredSuffixPatternMatch(),
				"Suffix pattern matching not supported with PathPatternParser.");
		}
		else {
			this.config.setSuffixPatternMatch(handlerMapping.useSuffixPatternMatch());
			this.config.setRegisteredSuffixPatternMatch(handlerMapping.useRegisteredSuffixPatternMatch());
			this.config.setPathMatcher(handlerMapping.getPathMatcher());
		}
	}

	@Override
	public ResponseEntity execute(@PathVariable(required = false) Map<String, String> pathVar,
															@RequestParam(required = false) Map<String, Object> param,
															@RequestBody(required = false) Map<String, Object> data,
															MultipartFile file,
								  							Map<String, String> extendParam,
															@ResponseResult ResponseData responseData,
															HttpServletRequest request, HttpServletResponse response) throws Throwable {
		ResponseEntity result = pluginContext.executePlugins(pathVar, param, data, file, extendParam, responseData, request, response);
		return result;
	}

	/**
	 * 注册
	 * @param pattern
	 * @param methods
	 */
	public void registerMapping(String pattern,List<RequestMethod> methods) throws NoSuchMethodException {
		registerMapping(pattern, methods, null);
	}

	/**
	 * 注册
	 * @param pattern
	 * @param methods
	 */
	public void registerMapping(String pattern,List<RequestMethod> methods, Class handlerClass) throws NoSuchMethodException {
		if(handlerClass == null) {
			handlerClass = RequestMappingService.class;
		}
		if(methods == null) {
			methods = new ArrayList<>();
		}
		if(methods.size() < 1) {
			methods.add(RequestMethod.GET);
		}
		RequestMappingInfo mappingInfo = RequestMappingInfo.paths(pattern)
			.methods(CollectionUtil.toArray(methods))
			.options(this.config)
			.build();
		Method targetMethod = handlerClass.getDeclaredMethod(
			"execute",
			Map.class,
			Map.class,
			Map.class,
			MultipartFile.class,
			Map.class,
			ResponseData.class,
			HttpServletRequest.class,
			HttpServletResponse.class
		);
		handlerMapping.registerMapping(mappingInfo, this, targetMethod);
	}

	/**
	 * 取消注册mapping
	 * @param pattern
	 * @param method
	 */
	public synchronized void cancelMapping(String pattern,String method) {
		RequestMappingInfo mappingInfo = RequestMappingInfo.paths(pattern)
			.methods(RequestMethod.valueOf(method))
			.options(this.config)
			.build();
		handlerMapping.unregisterMapping(mappingInfo);
	}
}
